

#include "SpriteBatch.h"

#include <System/Nullable.h>
#include <System/Diagnostics/Debug.h>
#include <Riccsson/Xna/Framework/Graphics/_shaders/BuiltinShaders.h>

#if Windows

// OpenGL + Windows includes.
#include <Windows.h>
#include <gl/GLee.h>

#else

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <UndefCSharpKeywords.h>
#include <jni.h>
#include <android/log.h>
#define  LOG_TAG    "jnitest"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

static void checkGlError2(const char* op)
{
    for (GLint error = glGetError(); error; error = glGetError())
	{
		LOGI("after %s() glError (0x%x)\n", op, error);
    }
}

#include <Defines.h>
#endif

using namespace Riccsson::System;
using namespace Riccsson::Xna::Framework::Graphics;

// ------------------------------------------------------------------------
SpriteBatch::SpriteBatch (GraphicsDevice* graphicsDevice)
{
						
}

// ------------------------------------------------------------------------
void SpriteBatch::Begin (SpriteSortMode sortMode, BlendState* blendState, SamplerState* samplerState, DepthStencilState* depthStencilState, RasterizerState* rasterizerState, Effect* effect, Riccsson::Xna::Framework::Matrix transformMatrix)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::Begin (SpriteSortMode sortMode, BlendState* blendState, SamplerState* samplerState, DepthStencilState* depthStencilState, RasterizerState* rasterizerState)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::Begin (SpriteSortMode sortMode, BlendState* blendState, SamplerState* samplerState, DepthStencilState* depthStencilState, RasterizerState* rasterizerState, Effect* effect)
{
	throw;
}
		
// ------------------------------------------------------------------------		
void SpriteBatch::Begin (SpriteSortMode sortMode, BlendState* blendState)
{
	// TODO: Not implemented.

	_sortMode = sortMode;

	if(_blendState != blendState)
	{
		_blendState = blendState;
	}
}

// ------------------------------------------------------------------------
void SpriteBatch::Begin ()
{
	_sortMode = SpriteSortMode::Deferred;
	_blendState = BlendState::AlphaBlend;
}

// ------------------------------------------------------------------------
void SpriteBatch::End ()
{

	if(_blendState == BlendState::NonPremultiplied)
	{
		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_ONE);
	}
	else if(_blendState == BlendState::AlphaBlend)
	{
		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_ONE);

		glUseProgram(g_spritebatch_opacity_program);
	}
	else if(_blendState == BlendState::Additive)
	{
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);

		glUseProgram(g_spritebatch_additive_program);
	}
	else
	{
		throw NotImplementedException();
	}

	int count( m_spriteBatchItems.Count );
	for (int i = 0; i < count; i++)
	{
		auto& batchItem = m_spriteBatchItems[i];

		// Texture source rectangle UV.
		float r = batchItem->vertexBL.color.R * 0.003921568627451f;
		float g = batchItem->vertexBL.color.G * 0.003921568627451f;
		float b = batchItem->vertexBL.color.B * 0.003921568627451f;
		float a = batchItem->vertexBL.color.A * 0.003921568627451f;

		// References:
		// https://groups.google.com/forum/#!topic/android-ndk/x_iyS1yHZFY
		// http://www.learnopengles.com/loading-a-png-into-memory-and-displaying-it-as-a-texture-with-opengl-es-2-using-almost-the-same-code-on-ios-android-and-emscripten/
	
		// 1,1
		// ___________ 3
		// |\		 |
		// | \		 |
		// |  \		 |
		// |   \	 |
		// |    \	 |
		// |	 \	 |
		// |	  \	 |
		// | 2     \ | 3, 2
		// |________\|

		GLfloat vVertices[] =
		{
			-1 + 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenWidth * batchItem->vertexTL.Position.X,
			1 - 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenHeight * batchItem->vertexTL.Position.Y, 0.0f, // Position 0

			batchItem->vertexTL.TextureCoordinate.X, batchItem->vertexTL.TextureCoordinate.Y, // TexCoord 0

			-1 + 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenWidth * batchItem->vertexBL.Position.X,
			1 - 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenHeight * batchItem->vertexBL.Position.Y, 0.0f, // Position 1

			batchItem->vertexBL.TextureCoordinate.X, batchItem->vertexBL.TextureCoordinate.Y, // TexCoord 1

			-1 + 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenWidth * batchItem->vertexBR.Position.X,
			1 - 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenHeight * batchItem->vertexBR.Position.Y, 0.0f, // Position 2
			
			batchItem->vertexBR.TextureCoordinate.X, batchItem->vertexBR.TextureCoordinate.Y, // TexCoord 2

			-1 + 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenWidth * batchItem->vertexTR.Position.X,
			1 - 2.0f / Riccsson::Xna::Framework::Graphics::GraphicsDevice::DeviceScreenHeight * batchItem->vertexTR.Position.Y, 0.0f, // Position 3

			batchItem->vertexTR.TextureCoordinate.X, batchItem->vertexTR.TextureCoordinate.Y // TexCoord 3
		};
		
		// Pass the vertex colours to the shader.
		const GLfloat gTriangleColor[] =
		{
			r, g, b, a,
			r, g, b, a,
			r, g, b, a,
			r, g, b, a
		};
	
		GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
		GLsizei stride = 5 * sizeof(GLfloat); // 3 for position, 2 for texture
	
		// Load the vertex position
		glVertexAttribPointer(g_spritebatch_opacity_gvPositionHandle, 3, GL_FLOAT, GL_FALSE, stride, vVertices);
		// Load the texture coordinate
		glVertexAttribPointer(g_spritebatch_opacity_texCoordSlot, 2, GL_FLOAT, GL_TRUE, stride, &vVertices[3]);
		// Load the texture color
		glVertexAttribPointer(g_spritebatch_opacity_iLocFillColor, 4, GL_FLOAT, GL_TRUE, 0, gTriangleColor);
	
		glEnableVertexAttribArray(g_spritebatch_opacity_gvPositionHandle);
		glEnableVertexAttribArray(g_spritebatch_opacity_texCoordSlot);
		glEnableVertexAttribArray(g_spritebatch_opacity_iLocFillColor);
	
		// Bind the texture
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, batchItem->Texture->GlTexture);
	
		// Set the sampler texture unit to 0
		glUniform1i(g_spritebatch_opacity_gvTexture, 0);

		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
	}

	glDisable(GL_BLEND);


	m_spriteBatchItemsOld.AddRange(&m_spriteBatchItems);
	m_spriteBatchItems.Clear();
}
		
// ------------------------------------------------------------------------
void SpriteBatch::Setup() 
{
	throw;
}
	
// ------------------------------------------------------------------------	
void SpriteBatch::CheckValid(Texture2D* texture)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::CheckValid(SpriteFont* spriteFont, const Riccsson::System::String* text)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::CheckValid(SpriteFont* spriteFont, Riccsson::System::Text::StringBuilder* text)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::Draw (Texture2D* texture,
	const Riccsson::Xna::Framework::Vector2& position,
	const Riccsson::System::Nullable<Riccsson::Xna::Framework::Rectangle>& sourceRectangle,
	const Riccsson::Xna::Framework::Color& color,
	float rotation,
	const Riccsson::Xna::Framework::Vector2& origin,
	const Riccsson::Xna::Framework::Vector2& scale,
	SpriteEffects effect,
	float depth)
{
	if (texture == null)
	{
		throw ArgumentException("texture");
	}

	SpriteBatchItem* item = getSpriteBatchItem();

	item->Depth = depth;
	item->Texture = texture;

	Rectangle rect;
	if ( sourceRectangle.HasValue)
		rect = sourceRectangle.Value;
	else
		rect = Rectangle( 0, 0, texture->Width, texture->Height );

	//Vector2 texCoordTL = texture->Image.GetTextureCoord ( rect.X, rect.Y );
	//Vector2 texCoordBR = texture->Image.GetTextureCoord ( rect.X+rect.Width, rect.Y+rect.Height );
	Vector2 texCoordTL(1.0f / texture->Width * rect.X, 1.0f / texture->Height * rect.Y);
	Vector2 texCoordBR(1.0f / texture->Width * (rect.X+rect.Width), 1.0f / texture->Height * (rect.Y+rect.Height));
	//tx1 = 1.0f / batchItem.Texture->Width * source.X;
	//ty1 = 1.0f / batchItem.Texture->Height * source.Y;
	//tx2 = 1.0f / batchItem.Texture->Width * source.Width;
	//ty2 = 1.0f / batchItem.Texture->Height * source.Height;

	if ( effect == SpriteEffects::FlipVertically )
	{
		float temp = texCoordBR.Y;
		texCoordBR.Y = texCoordTL.Y;
		texCoordTL.Y = temp;
	}
	else if ( effect == SpriteEffects::FlipHorizontally )
	{
		float temp = texCoordBR.X;
		texCoordBR.X = texCoordTL.X;
		texCoordTL.X = temp;
	}

	item->Set
		(
			position.X,
			position.Y,
			-origin.X * scale.X,
			-origin.Y * scale.Y,
			rect.Width * scale.X,
			rect.Height * scale.Y,
			(float)Math::Sin(rotation),
			(float)Math::Cos(rotation),
			color,
			texCoordTL,
			texCoordBR
			);
}

// ------------------------------------------------------------------------
void SpriteBatch::Draw (Texture2D* texture,
	const Riccsson::Xna::Framework::Vector2& position,
	const Riccsson::System::Nullable<Riccsson::Xna::Framework::Rectangle>& sourceRectangle,
	const Riccsson::Xna::Framework::Color& color,
	float rotation,
	const Riccsson::Xna::Framework::Vector2& origin,
	float scale,
	SpriteEffects effect,
	float depth)
{
	if (texture == null )
	{
		throw ArgumentException("texture");
	}

	SpriteBatchItem* item = getSpriteBatchItem();

	item->Depth = depth;
	item->Texture = texture;

	Rectangle rect;
	if ( sourceRectangle.HasValue)
		rect = sourceRectangle.Value;
	else
		rect = Rectangle( 0, 0, texture->Width, texture->Height );
	
	Vector2 texCoordTL(1.0f / texture->Width * rect.X, 1.0f / texture->Height * rect.Y);
	Vector2 texCoordBR(1.0f / texture->Width * (rect.X+rect.Width), 1.0f / texture->Height * (rect.Y+rect.Height));

	if ( effect == SpriteEffects::FlipVertically )
	{
		float temp = texCoordBR.Y;
		texCoordBR.Y = texCoordTL.Y;
		texCoordTL.Y = temp;
	}
	else if ( effect == SpriteEffects::FlipHorizontally )
	{
		float temp = texCoordBR.X;
		texCoordBR.X = texCoordTL.X;
		texCoordTL.X = temp;
	}

	item->Set
		(
			position.X,
			position.Y,
			-origin.X*scale,
			-origin.Y*scale,
			rect.Width*scale,
			rect.Height*scale,
			(float)Math::Sin(rotation),
			(float)Math::Cos(rotation),
			color,
			texCoordTL,
			texCoordBR
			);
}

// ------------------------------------------------------------------------
void SpriteBatch::Draw (Texture2D* texture,
	const Riccsson::Xna::Framework::Rectangle& destinationRectangle,
	const Riccsson::System::Nullable<Riccsson::Xna::Framework::Rectangle>& sourceRectangle,
	const Riccsson::Xna::Framework::Color& color,
	float rotation,
	const Riccsson::Xna::Framework::Vector2& origin,
	SpriteEffects effect,
	float depth)
{
	if (texture == null )
	{
		throw ArgumentException("texture");
	}

	SpriteBatchItem* item = getSpriteBatchItem();

	item->Depth = depth;
	item->Texture = texture;

	Rectangle rect;
	if ( sourceRectangle.HasValue)
		rect = sourceRectangle.Value;
	else
		rect = Rectangle( 0, 0, texture->Width, texture->Height );
	
	Vector2 texCoordTL(1.0f / texture->Width * rect.X, 1.0f / texture->Height * rect.Y);
	Vector2 texCoordBR(1.0f / texture->Width * (rect.X+rect.Width), 1.0f / texture->Height * (rect.Y+rect.Height));

	if ( effect == SpriteEffects::FlipVertically )
	{
		float temp = texCoordBR.Y;
		texCoordBR.Y = texCoordTL.Y;
		texCoordTL.Y = temp;
	}
	else if ( effect == SpriteEffects::FlipHorizontally )
	{
		float temp = texCoordBR.X;
		texCoordBR.X = texCoordTL.X;
		texCoordTL.X = temp;
	}

	item->Set 
		( 
			destinationRectangle.X, 
			destinationRectangle.Y, 
			-origin.X * ((float)destinationRectangle.Width / (float)texture->Width), 	// Corrected.
			-origin.Y * ((float)destinationRectangle.Height / (float)texture->Height), 	// Corrected.
			destinationRectangle.Width,
			destinationRectangle.Height,
			(float)Math::Sin(rotation),
			(float)Math::Cos(rotation),
			color,
			texCoordTL,
			texCoordBR );	
}

// ------------------------------------------------------------------------
/*internal*/ void SpriteBatch::DrawInternal (Texture2D* texture,
	const Riccsson::Xna::Framework::Vector4& destinationRectangle,
	const Riccsson::System::Nullable<Riccsson::Xna::Framework::Rectangle>& sourceRectangle,
	const Riccsson::Xna::Framework::Color& color,
	float rotation,
	const Riccsson::Xna::Framework::Vector2& origin,
	SpriteEffects effect,
	float depth)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::Draw(Texture2D* texture, const Riccsson::Xna::Framework::Vector2& position, const Riccsson::System::Nullable<Riccsson::Xna::Framework::Rectangle>& sourceRectangle, const Riccsson::Xna::Framework::Color& color)
{
	if (texture == null )
	{
		throw ArgumentException("texture");
	}

	SpriteBatchItem* item = getSpriteBatchItem();

	item->Depth = 0.0f;
	item->Texture = texture;

	Rectangle rect;
	if ( sourceRectangle.HasValue)
		rect = sourceRectangle.Value;
	else
		rect = Rectangle( 0, 0, texture->Width, texture->Height );
	
	Vector2 texCoordTL(1.0f / texture->Width * rect.X, 1.0f / texture->Height * rect.Y);
	Vector2 texCoordBR(1.0f / texture->Width * (rect.X+rect.Width), 1.0f / texture->Height * (rect.Y+rect.Height));

	item->Set ( position.X, position.Y, rect.Width, rect.Height, color, texCoordTL, texCoordBR );
}

// ------------------------------------------------------------------------
void SpriteBatch::Draw(Texture2D* texture, const Riccsson::Xna::Framework::Rectangle& destinationRectangle, const Riccsson::System::Nullable<Riccsson::Xna::Framework::Rectangle>& sourceRectangle, const Riccsson::Xna::Framework::Color& color)
{
	if (texture == null )
	{
		throw ArgumentException("texture");
	}

	SpriteBatchItem* item = getSpriteBatchItem();

	item->Depth = 0.0f;
	item->Texture = texture;

	Rectangle rect;
	if ( sourceRectangle.HasValue)
		rect = sourceRectangle.Value;
	else
		rect = Rectangle( 0, 0, texture->Width, texture->Height );
	
	Vector2 texCoordTL(1.0f / texture->Width * rect.X, 1.0f / texture->Height * rect.Y);
	Vector2 texCoordBR(1.0f / texture->Width * (rect.X+rect.Width), 1.0f / texture->Height * (rect.Y+rect.Height));

	item->Set 
		( 
			destinationRectangle.X, 
			destinationRectangle.Y, 
			destinationRectangle.Width, 
			destinationRectangle.Height, 
			color, 
			texCoordTL, 
			texCoordBR );
}

// ------------------------------------------------------------------------
void SpriteBatch::Draw(Texture2D* texture, const Riccsson::Xna::Framework::Vector2& position, const Riccsson::Xna::Framework::Color& color)
{
	if (texture == null )
	{
		throw ArgumentException("texture");
	}

	SpriteBatchItem* item = getSpriteBatchItem();

	item->Depth = 0;
	item->Texture = texture;

	Rectangle rect = Rectangle( 0, 0, texture->Width, texture->Height );
	
	Vector2 texCoordTL(1.0f / texture->Width * rect.X, 1.0f / texture->Height * rect.Y);
	Vector2 texCoordBR(1.0f / texture->Width * (rect.X+rect.Width), 1.0f / texture->Height * (rect.Y+rect.Height));

	item->Set 
		(
			position.X,
			position.Y,
			rect.Width,
			rect.Height,
			color,
			texCoordTL,
			texCoordBR
			);
}

// ------------------------------------------------------------------------
SpriteBatchItem* SpriteBatch::getSpriteBatchItem()
{
	SpriteBatchItem* item;
	int oldCount(m_spriteBatchItemsOld.Count);
	if (oldCount > 0)
	{
		item = m_spriteBatchItemsOld[oldCount - 1];
		m_spriteBatchItems.Add(item);
		m_spriteBatchItemsOld.RemoveAt(oldCount - 1);
	}
	else
	{
		// Create new item.
		item = new SpriteBatchItem();
		m_spriteBatchItems.Add(item);
	}

	return item;
}

// ------------------------------------------------------------------------
void SpriteBatch::Draw(Texture2D* texture, const Riccsson::Xna::Framework::Rectangle& rectangle, const Riccsson::Xna::Framework::Color& color)
{
	if (texture == null )
	{
		throw ArgumentException("texture");
	}

	SpriteBatchItem* item = getSpriteBatchItem();
	item->Depth = 0;
	item->Texture = texture;
	
	Rectangle rect = Rectangle( 0, 0, texture->Width, texture->Height );

	Vector2 texCoordTL(1.0f / texture->Width * rect.X, 1.0f / texture->Height * rect.Y);
	Vector2 texCoordBR(1.0f / texture->Width * (rect.X+rect.Width), 1.0f / texture->Height * (rect.Y+rect.Height));

	item->Set
		(
			rectangle.X,
			rectangle.Y,
			rectangle.Width,
			rectangle.Height,
			color,
			texCoordTL,
			texCoordBR
		);
}

// ------------------------------------------------------------------------
void SpriteBatch::DrawString(SpriteFont* spriteFont, Riccsson::System::String* text, Riccsson::Xna::Framework::Vector2& position, Riccsson::Xna::Framework::Color& color)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::DrawString (
	SpriteFont* spriteFont, const Riccsson::System::String* text, Riccsson::Xna::Framework::Vector2& position, Riccsson::Xna::Framework::Color& color,
	float rotation, Riccsson::Xna::Framework::Vector2& origin, float scale, SpriteEffects effects, float depth)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::DrawString (
	SpriteFont* spriteFont, const Riccsson::System::String* text, Riccsson::Xna::Framework::Vector2& position, Riccsson::Xna::Framework::Color& color,
	float rotation, Riccsson::Xna::Framework::Vector2& origin, Riccsson::Xna::Framework::Vector2& scale, SpriteEffects effect, float depth)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::DrawString (SpriteFont* spriteFont, Riccsson::System::Text::StringBuilder* text, Riccsson::Xna::Framework::Vector2& position, Riccsson::Xna::Framework::Color& color)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::DrawString (
	SpriteFont* spriteFont, Riccsson::System::Text::StringBuilder* text, Riccsson::Xna::Framework::Vector2& position, Riccsson::Xna::Framework::Color& color,
	float rotation, Riccsson::Xna::Framework::Vector2& origin, float scale, SpriteEffects effects, float depth)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::DrawString (
	SpriteFont* spriteFont, Riccsson::System::Text::StringBuilder* text, Riccsson::Xna::Framework::Vector2& position, Riccsson::Xna::Framework::Color& color,
	float rotation, Riccsson::Xna::Framework::Vector2& origin, Riccsson::Xna::Framework::Vector2& scale, SpriteEffects effect, float depth)
{
	throw;
}

// ------------------------------------------------------------------------
void SpriteBatch::Dispose(bool disposing)
{
	throw;
}